package comp6231.project.replica;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.ArrayList;

import comp6231.project.common.Constants;
import comp6231.project.common.Message;
import comp6231.project.common.ReplicaState;

import comp6231.project.common.Utils;


public class DRS extends Thread {

	private Replica[] replicas; 
	private final static int REPLICA_SIZE = 3;
	private String replicaImplName;
	
	private String RM_Name = "localhost";
	private int RM_port = 9000;

	
	
	/**
	 * 
	 * @param replicaImplName D for dimitri's, K for kelvin's, T for thomas's
	 */
	public DRS(String replicaImplName){
		this.replicaImplName = replicaImplName;
	}
	
	/**
	 * 
	 * @param replicaEdition 1 for dimitri's version, 2 for kelvin's version, 3 for thomas's version.
	 * @throws Exception
	 */

 	public void initialDRS() throws Exception {
 		
 		replicas = new Replica[3];
 		if(this.replicaImplName.equals("D")){
 			replicas[0] = ReplicaFactory.getReplica("M1");
 			replicas[1] = ReplicaFactory.getReplica("T1");
 			replicas[2] = ReplicaFactory.getReplica("V1");
 		}
 		if(this.replicaImplName.equals("K")){
 			replicas[0] = ReplicaFactory.getReplica("M2");
 			replicas[1] = ReplicaFactory.getReplica("T2");
 			replicas[2] = ReplicaFactory.getReplica("V2");
 		}
 		if(this.replicaImplName.equals("T")){
 			replicas[0] = ReplicaFactory.getReplica("M3");
 			replicas[1] = ReplicaFactory.getReplica("T3");
 			replicas[2] = ReplicaFactory.getReplica("V3");
 		}
 		
 		replicas[0].start();
 		replicas[1].start();
 		replicas[2].start();
 		
	}
 	
 	/**
 	 * 
 	 * @param replicaIndex 0 for montreal, 1 for toronto, 2 for vancouver
 	 */
 	public void deleteReplica(int replicaIndex){
 		try {
	 		if(replicaIndex > REPLICA_SIZE-1 || replicaIndex < 0)	{
	 			System.out.println("no id match");
	 			return;
	 		}
	 		replicas[replicaIndex].stop = true;
	 		replicas[replicaIndex].interrupt();
	 		replicas[replicaIndex] = null;
	 		System.gc();
	 		this.sleep(1000);
	 		System.out.println("Deleted replica at index " + replicaIndex);
 		} catch (Exception ex) {
 			ex.printStackTrace();
 		}
 	}
 	
 	
// 	public void recreateReplica(int replicaId) throws Exception{
// 		if(replicaId > REPLICA_SIZE-1 || replicaId < 0)	{
// 			System.out.println("no id match");
// 			return;
// 		}
// 		replicas[replicaId] = ReplicaFactory.getReplica(this.replicaName);
// 		if(replicaId == 0){
// 			replicas[replicaId].setReplicaState(replicas[1].getReplicaState());
// 		}
// 		else
// 			replicas[replicaId].setReplicaState(replicas[0].getReplicaState());
// 		replicas[replicaId].start();
//
// 	}
 	
 	private int getReplicaIndex(String replicaId) {
 		if (replicaId.startsWith("M")) {
 			return 0;
 		} else if (replicaId.startsWith("T")) {
 			return 1;
 		} else {
 			return 2;
 		}
 	}
	
	/**
	 * 
	 * @param replicaIndex 0 for montreal, 1 for toronto, 2 for vancouver
	 * @return the state info of store
	 */
	private ReplicaState getReplicaState(int replicaIndex) {
		if(replicaIndex > REPLICA_SIZE-1 || replicaIndex < 0)	{
 			System.out.println("no id match");
 			return null;
 		}
		return replicas[replicaIndex].getReplicaState();

	}
	
	private void recreateReplicaWithState(String replicaID, ReplicaState state) throws Exception {
		int replicaIndex = getReplicaIndex(replicaID);
		deleteReplica(replicaIndex);
		replicas[replicaIndex] = ReplicaFactory.reCreateReplica(replicaID, state);
	}
	
	public void waitForMessagesFromRM() {
		DatagramSocket dsIn = null;
		DatagramSocket dsOut = null;
		try {
			// Wait for messages from RM
			dsIn = new DatagramSocket(Constants.DRS_PORT);
			while (true) {
				try {
					byte bufIn[] = new byte[Utils.UDP_DATAGRAM_MAX_SIZE];
					DatagramPacket pack = new DatagramPacket(bufIn,
							bufIn.length);
					dsIn.receive(pack);
					Object obj = Utils.fromBytes(bufIn);
					if (obj instanceof Message) {
						Message msg = (Message)obj;
						if (msg.request.equals(Message.REQUEST_GETREPLSTATE)) {
							// Send to RM the requested replica state
							int replicaIndex = getReplicaIndex(msg.replicaID);
							ReplicaState state = getReplicaState(replicaIndex);
							dsOut = new DatagramSocket();
							byte bufOut[] = Utils.toBytes(state);
							pack = new DatagramPacket(bufOut, bufOut.length, pack
									.getAddress(), pack.getPort());
							dsOut.send(pack);
							
						} else if (msg.request.equals(Message.REQUEST_REPLREPLICA)) {
							// Receive replica state from RM for replacement
							pack = new DatagramPacket(bufIn,
									bufIn.length);
							dsIn.receive(pack);
							obj = Utils.fromBytes(bufIn);
							if (obj instanceof ReplicaState) {
								ReplicaState state = (ReplicaState)obj;
								recreateReplicaWithState(msg.replicaID, state);
								
							}
							
							// Notify RM replacement of replica is done
							dsOut = new DatagramSocket();
							msg.request = Message.REQUEST_REPL_ACK;
							byte bufOut[] = Utils.toBytes(msg);
							pack = new DatagramPacket(bufOut, bufOut.length, pack
									.getAddress(), pack.getPort());
							dsOut.send(pack);
						}
					}

				} catch (Exception ex) {
					ex.printStackTrace();
				}
			}
		} catch (Exception ex) {
			ex.printStackTrace();

		} finally {
			if (dsIn != null) {
				dsIn.close();
			}
			if (dsOut != null) {
				dsOut.close();
			}
		}		
	}	
	
	//two way handshake used to tell RM the address.
	public void run(){
		try{
		String message = "Hello";
		DatagramSocket sScoket = new DatagramSocket();
		InetAddress IPAddress = InetAddress.getByName(RM_Name);
		byte[] sendData = new byte[1024];
		byte[] receiveData = new byte[1024];
		sendData = message.getBytes();
		DatagramPacket sendPacket = new DatagramPacket(sendData,
				sendData.length, IPAddress, RM_port);
		sScoket.send(sendPacket);
		
		DatagramPacket receivePacket = new DatagramPacket(receiveData,
				receiveData.length);
		sScoket.receive(receivePacket);
		
		String receiveContent = new String(receivePacket.getData());
		sScoket.close();
		
		}catch(Exception e){
			e.printStackTrace();
			
		}
			
	}
	
	
	
	
	public static void main(String[] args) throws Exception{
		System.out.println("Please implementation name for replicas (D for dimitri's, K for kelvin's, T for thomas's):");
		InputStreamReader is	= new InputStreamReader(System.in);
		BufferedReader br 		= new BufferedReader(is);
		String replicaImplName = br.readLine().trim();
		
		DRS drs1 = new DRS(replicaImplName);
		drs1.initialDRS();		
		drs1.waitForMessagesFromRM();
		
		//drs1.deleteReplica(0);
		//drs1.deleteReplica(1);
		//drs1.deleteReplica(2);
		
		
//		String replicaName2 = "D";
//		DRS drs2 = new DRS(replicaName2);
//		drs2.initialDRS();
		
		
//		DRS drs1 = new DRS("Montreal");
//		drs1.initialDRS();
//		drs1.deleteReplica(0);
//		drs1.recreateReplica(0);
////		DRS drs2 = new DRS("Toronto");
	}

}
